from datetime import datetime

import uvicorn
from fastapi import FastAPI, Depends, HTTPException, Request, Header
from starlette.responses import JSONResponse

import schemas
from sqlalchemy.orm import Session

from chart import get_record_chart, get_card_chart, get_project_chart, get_stats_chart
from database import SessionLocal, engine
import models
from jpush import push_notify

models.Base.metadata.create_all(bind=engine)
app = FastAPI()


@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    if request.url.path == "/status":
        return await call_next(request)
    uid = request.headers.get("uid")
    if uid is None:
        return JSONResponse(status_code=401, content={"reason": "Unauthorized"})
    try:
        db = request.state.db = SessionLocal()

        if request.url.path != "/login":
            user = request.state.user = db.query(models.User).filter(models.User.id == uid, models.User.state == True).first()
            if user is None:
                return JSONResponse(status_code=401, content={"reason": "Unauthorized"})
        response = await call_next(request)
    finally:
        db.close()
    return response


# Dependency
def get_db(request: Request):
    return request.state.db


def get_login_user(request: Request):
    return request.state.user


@app.get("/status")
async def status():
    return 'ok'


@app.get("/login", response_model=schemas.User)
def get_user_by_did(device_id: str, platform: str = Header(None), db: Session = Depends(get_db)):
    dq = db.query(models.User)
    if platform == "android":
        dq = dq.filter(models.User.deviceId == device_id, models.User.state == True)
    else:
        dq = dq.filter(models.User.iosDeviceId == device_id, models.User.state == True)
    user = dq.first()
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return user


@app.post("/login", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
    exit_user = db.query(models.User).filter(models.User.phone == user.phone, models.User.state == True).first()
    if exit_user is not None:
        return exit_user
    db.add(user)
    db.commit()
    db.refresh(user)
    return user


@app.get("/user/{user_id}", response_model=schemas.User)
def get_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(models.User).filter(models.User.id == user_id, models.User.state == True).first()
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    return user


@app.put("/user/{user_id}", response_model=schemas.User)
def update_user(user: schemas.UserUpdate, db: Session = Depends(get_db),
                login_user: models.User = Depends(get_login_user)):  # type: ignore
    login_user.nickname = user.nickname
    login_user.pushId = user.pushId
    login_user.updated_at = datetime.now()
    db.commit()
    db.refresh(login_user)
    return login_user


@app.get('/record', response_model=list[schemas.Record])
def get_records(db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user),
                filter: str | None = None, start: str | None = None, end: str | None = None):
    if filter == 'self':
        query = login_user.records
    else:
        scoped_ids = login_user.scoped_ids()
        query = db.query(models.Record).order_by(models.Record.created_at.desc()).filter(
            models.Record.user_id.in_(scoped_ids))
    if start is not None:
        query = query.filter(models.Record.created_at >= start)
    if end is not None:
        query = query.filter(models.Record.created_at <= end)
    records = query.filter(models.Record.state == True).all()
    for record in records:
        is_self = record.user_id == login_user.id
        record.metadata = {'isSelf': is_self, 'creator': '我' if is_self else login_user.find_nickname(record.user_id)}
    return records


@app.post('/record', response_model=schemas.Record)
def create_record(record: schemas.RecordBase, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):  # type: ignore
    db_record = models.Record(**record.dict(), user_id=login_user.id)
    db.add(db_record)
    db.commit()
    db.refresh(db_record)
    db_record.metadata = {'isSelf': True, 'creator': '我'}
    bank_str = ''
    if record.card_id is not None:
        db_card = login_user.cards.filter(models.Card.id == record.card_id).first()
        if db_card is not None:
            db_card.fee -= record.fee
            db_card.updated_at = datetime.now()
            db.commit()
            bank_str = ", 扣款来自[{}]".format(db_card.bank)
    push_notify('消费提醒', '消费了{}元用于[{}]{}{}'.format(db_record.fee, db_record.category, db_record.description, bank_str), db_record, login_user)
    return db_record


@app.delete('/record/{record_id}')
def delete_record(record_id: int, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    record = login_user.records.filter(models.Record.id == record_id).first()
    if record is None:
        raise HTTPException(status_code=404, detail="Record not found")
    db.delete(record)
    db.commit()
    if record.card_id is not None:
        db_card = login_user.cards.filter(models.Card.id == record.card_id).first()
        if db_card is not None:
            db_card.fee += record.fee
            db_card.updated_at = datetime.now()
            db.commit()
    return {'ok'}


@app.get('/category', response_model=list[schemas.Category])
def get_categories(login_user: models.User = Depends(get_login_user)):
    return login_user.categories.filter(models.Category.state == True)


@app.post('/category', response_model=schemas.Category)
def create_category(category: schemas.CategoryBase, db: Session = Depends(get_db),
                    login_user: models.User = Depends(get_login_user)):  # type: ignore
    db_category = models.Category(**category.dict(), user_id=login_user.id)
    db.add(db_category)
    db.commit()
    db.refresh(db_category)
    return db_category


@app.delete('/category/{category_id}')
def delete_category(category_id: int, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    category = login_user.categories.filter(models.Category.id == category_id).first()
    if category is None:
        raise HTTPException(status_code=404, detail="Category not found")
    db.delete(category)
    db.commit()
    return {'ok'}


@app.get("/project", response_model=list[schemas.Project])
def get_project(uid: int | None = None, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    if uid is None:
        return login_user.projects.filter(models.Project.state == True)
    else:
        scoped_ids = login_user.scoped_ids()
        if uid not in scoped_ids:
            raise HTTPException(status_code=403, detail="Forbidden")
        return db.query(models.Project).filter(models.Project.state == True, models.Project.user_id == uid).order_by(models.Project.updated_at.desc()).all()


@app.post("/project", response_model=schemas.Project)
def create_project(project: schemas.ProjectBase, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):  # type: ignore
    db_project = models.Project(**project.dict(), user_id=login_user.id)
    db.add(db_project)
    db.commit()
    db.refresh(db_project)
    push_notify('理财项目新增提醒', '新增了[{}]项目, 本金为¥{}, 预计收益为¥{}'.format(db_project.name, round(db_project.fee, 2), round(db_project.income, 2)), db_project, login_user)
    return db_project


@app.put("/project/{project_id}", response_model=schemas.Project)
def update_project(project_id: int, project: schemas.ProjectBase, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    db_project = login_user.projects.filter(models.Project.id == project_id).first()
    if project is None:
        raise HTTPException(status_code=404, detail="Project not found")
    db_project.name = project.name
    db_project.rate = project.rate
    db_project.fee = project.fee
    db_project.income = project.income
    db_project.startTime = project.startTime
    db_project.endTime = project.endTime
    db_project.updated_at = datetime.now()
    db.commit()
    db.refresh(db_project)
    push_notify('理财项目更新提醒', '更新了[{}]项目, 本金为¥{}, 最新预计收益为¥{}'.format(db_project.name, round(db_project.fee, 2), round(db_project.income, 2)), db_project, login_user);
    return db_project


@app.delete("/project/{project_id}")
def delete_project(project_id: int, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    project = login_user.projects.filter(models.Project.id == project_id).first()
    if project is None:
        raise HTTPException(status_code=404, detail="Project not found")
    project.state = False
    db.commit()
    return {'ok'}


@app.get("/card", response_model=list[schemas.Card])
def get_card(uid: int | None = None, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    if uid is None:
        return login_user.cards.filter(models.Card.state == True)
    else:
        scoped_ids = login_user.scoped_ids()
        if uid not in scoped_ids:
            raise HTTPException(status_code=403, detail="Forbidden")
        return db.query(models.Card).filter(models.Card.state == True, models.Card.user_id == uid).order_by(models.Card.updated_at.desc()).all()


@app.post("/card", response_model=schemas.Card)
def create_card(card: schemas.CardBase, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    db_card = models.Card(**card.dict(), user_id=login_user.id)
    db.add(db_card)
    db.commit()
    db.refresh(db_card)
    push_notify('银行卡新增提醒', '新增了一张{}的银行卡, 余额为¥{}'.format(db_card.bank, round(db_card.fee, 2)), db_card, login_user)
    return db_card


@app.put("/card/{card_id}", response_model=schemas.Card)
def update_card(card_id: int, card: schemas.CardBase, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    db_card = login_user.cards.filter(models.Card.id == card_id).first()
    if db_card is None:
        raise HTTPException(status_code=404, detail="Card not found")
    db_card.fee = card.fee
    db_card.bank = card.bank
    db_card.updated_at = datetime.now()
    db.commit()
    db.refresh(db_card)
    push_notify('银行卡更新提醒', '更新{}的银行卡, 最新余额为¥{}'.format(db_card.bank, round(db_card.fee, 2)), db_card, login_user)
    return db_card


@app.delete("/card/{card_id}")
def delete_card(card_id: int, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    card = login_user.cards.filter(models.Card.id == card_id).first()
    if card is None:
        raise HTTPException(status_code=404, detail="Card not found")
    card.state = False
    db.commit()
    return {'ok'}


@app.get("/family", response_model=list[schemas.Family])
def get_family(login_user: models.User = Depends(get_login_user)):
    return login_user.families.filter(models.Family.state == True)


@app.post("/family", response_model=schemas.Family)
def create_family(family: schemas.FamilyCreate, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    exist = db.query(models.Family).filter(models.Family.user_id == login_user.id, models.Family.family_id == family.family.id).first()
    if exist is not None:
        if exist.state is False:
            exist.state = True
            db.commit()
            db.refresh(exist)
        return exist
    user = db.query(models.User).filter(models.User.id == family.family.id).first()
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")
    db_family = models.Family(family_id=family.family.id, user_id=login_user.id, nickname=user.nickname)
    db.add(db_family)
    db.commit()
    db.refresh(db_family)
    return db_family


@app.put("/family/{family_id}", response_model=schemas.Family)
def update_family(family_id: int, family: schemas.FamilyBase, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    db_family = login_user.families.filter(models.Family.id == family_id).first()
    if db_family is None:
        raise HTTPException(status_code=404, detail="Family not found")
    db_family.nickname = family.nickname
    db_family.updated_at = datetime.now()
    db.commit()
    return db_family


@app.delete("/family/{family_id}")
def delete_family(family_id: int, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    db_family = login_user.families.filter(models.Family.id == family_id).first()
    if db_family is None:
        raise HTTPException(status_code=404, detail="Family not found")
    db_family.state = False
    db.commit()
    return {'ok'}


@app.get("/chart/{type}")
def get_chart(type: str, db: Session = Depends(get_db), login_user: models.User = Depends(get_login_user)):
    scoped_ids = login_user.scoped_ids()
    if type == 'record':
        return get_record_chart(db, scoped_ids)
    elif type == 'project':
        return get_project_chart(db, scoped_ids)
    elif type == 'card':
        return get_card_chart(db, scoped_ids)
    elif type == 'statistics':
        return get_stats_chart(db, scoped_ids)
    else:
        raise HTTPException(status_code=404, detail="Chart not found")


@app.get("/app/{platform}", response_model=schemas.App)
def get_app(platform: str, db: Session = Depends(get_db)):
    res = db.query(models.App).filter(models.App.platform == platform).order_by(models.App.created_at.desc()).first()
    if res is None:
        raise HTTPException(status_code=404, detail="App not found")
    else:
        return res


#if __name__ == '__main__':
#    uvicorn.run('main:app', host='0.0.0.0', port=8000, reload=True, log_level="info")
